package main

import (
	"fmt"
	"reflect"
)

/*
  不要乱用反射
	反射是一个强大并富有表现力的工具，能让我们写出更灵活的代码。但反射不应该被滥用，原因：
	1、给予反射的代码非常脆弱，反射中的类型错误会在真正运行的时候才会引发panic，那很可能是在代码写完的很长时间之后。
	2、大量使用反射的代码通常难以理解
*/

type Student struct {
	Name  string `json:"name" test:"myTest"`
	Age   int    `json:"age"`
	Score int    `json:"score"`
}

func (s *Student) SetInfo(name string, age int, score int) {
	s.Name = name
	s.Age = age
	s.Score = score
}

func (s Student) GetInfo() string {
	var str = fmt.Sprintf("姓名：%s，年龄：%d，分数：%d", s.Name, s.Age, s.Score)
	return str
}

func (s Student) Print() {
	fmt.Println("这是一个打印方法。。")
}

func printStructField(s interface{}) {
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)
	//判断参数是不是结构体类型
	if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的参数不是一个结构体")
		return
	}
	//1、通过类型变量里面的Field获取结构体字段
	field0 := t.Field(0)
	fmt.Printf("%#v\n", field0)         //reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x1047935c0), Tag:"json:\"name\"", Offset:0x0, Index:[]int{0}, Anonymous:false}
	fmt.Println(field0)                 //{Name  string json:"name" 0 [0] false}
	fmt.Println(field0.Name)            // Name
	fmt.Println(field0.Type)            // string
	fmt.Println(field0.Tag)             //json:"name"
	fmt.Println(field0.Tag.Get("json")) // name
	fmt.Println(field0.Tag.Get("test")) // myTest
	//2、	通过类型变量里面的FieldByName可以获取结构体的字段
	fieldByName0, ok := t.FieldByName("Age")
	if ok {
		fmt.Println("字段名称:", fieldByName0.Name)
		fmt.Println("字段类型:", fieldByName0.Type)
		fmt.Println("字段Tag:", fieldByName0.Tag.Get("json"))
	}
	//3、通过类型变量里面的NameField获取到该结构体有几个字段
	fmt.Println("结构体字段个数:", t.NumField()) //3

	//4、获取结构体对应的值
	fieldValue := v.FieldByName("Name")
	fmt.Println(fieldValue) //小罗

	fmt.Println("-------------")
	for i := 0; i < t.NumField(); i++ {
		fmt.Println("字段名：", t.Field(i).Name)
		fmt.Println("字段类型：", t.Field(i).Type)
		fmt.Println("字段Tag：", t.Field(i).Tag)
		fmt.Println("字段值：", v.Field(i))
	}
}

func printStructFn(s interface{}) {
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)
	//判断参数是不是结构体类型
	if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的参数不是一个结构体")
		return
	}
	//1、通过类型变量里的Method可以获取结构体的方法
	method0 := t.Method(0) //和结构体的顺序没有关系，和结构体方法的ASCII码值有关
	fmt.Println(method0)
	fmt.Println(method0.Name)
	fmt.Println(method0.Type)

	//2、通过类型变量获取这个结构体有多少个方法
	method1, ok := t.MethodByName("Print")
	if ok {
		fmt.Println(method1.Name, method1.Type)
	}

	fmt.Println("-------")
	//3、通过《值变量》执行方法（注意需要使用变量，并且要注意参数）
	// v.Method(1).Call(nil)
	v.MethodByName("Print").Call(nil)

	info := v.MethodByName("GetInfo").Call(nil)
	fmt.Println(info) //[姓名：小罗，年龄：23，分数：100]
	fmt.Println("-------")
	//4、执行方法传入参数 （注意需要使用（值变量），并且要注意参数，接受的参数是[]reflect.value的切片）
	var params []reflect.Value
	params = append(params, reflect.ValueOf("李四"))
	params = append(params, reflect.ValueOf(23))
	params = append(params, reflect.ValueOf(89))
	v.MethodByName("SetInfo").Call(params)
	info2 := v.MethodByName("GetInfo").Call(nil)
	fmt.Println(info2)

	//5、方法数量
	fmt.Println("方法数量", t.NumMethod())
}

func reflectChangeStruct(s interface{}) {
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)
	if t.Kind() != reflect.Ptr {
		fmt.Println(s, "不是指针类型")
		return
	} else {
		if t.Elem().Kind() != reflect.Struct {
			fmt.Println("传入的不是结构体指针类型")
			return
		}
	}
	//修改结构体属性的值
	name := v.Elem().FieldByName("Name")
	name.SetString("小牛")
	age := v.Elem().FieldByName("Age")
	age.SetInt(61)
	score := v.Elem().FieldByName("Score")
	score.SetInt(100)

}

func main() {
	stu1 := Student{
		Name:  "小罗",
		Age:   23,
		Score: 100,
	}
	// printStructField(stu1)
	printStructFn(&stu1)
	fmt.Printf("%#v\n", stu1) //main.Student{Name:"李四", Age:23, Score:89}

	reflectChangeStruct(&stu1)

	fmt.Println(stu1)
}
